# Copyright 2007 Eloff Enterprises
#
# Licensed under the BSD License (the "License").
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://rwig.googlecode.com/files/LICENSE
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.  See the License for the specific language governing
# permissions and limitations under the License.
#
# Originally developed by Michele Simionato.

import optparse, os, sys

def bool_build(val, table=(frozenset(['off','no','0','false']),
                           frozenset(['on','yes','1','true']))):
    val = val.lower()
    if val in table[True]:
        return True
    elif val in table[False]:
        return False
    else:
        raise ValueError, 'Could not convert "%s" to bool' % val

def find_in_lines(lines, s):
    for i, line in enumerate(lines):
        if s in line:
            return i
        
    return -1

def nonzero(self): # will become the nonzero method of optparse.Values       
    "True if options were given"
    for v in self.__dict__.itervalues():
        if v is not None: return True
    return False

optparse.Values.__nonzero__ = nonzero # dynamically fix optparse.Values

class ParsingError(Exception): 
    pass     

def parse(docstr, arglist=None):
    ''' Parses the doc string for program usage information 
    and uses that to parse the command line arguments. '''
    
    optlines = docstr.splitlines()
    end_usage = find_in_lines(optlines, 'usage:')
    if end_usage == -1:
        raise ParsingError('Cannot find the usage string')
    else:
        end_usage += 1 # Usage ends on the line immediately following the usage: line.
    
    start_opts = find_in_lines(optlines, 'options:')
    if start_opts == -1:
        start_opts = end_usage
    else:
        end_usage = start_opts
        start_opts += 1 # Optiosn start on the line immediately following the options: line.
    
    usage = os.linesep.join(optlines[:end_usage])
    optlines = optlines[start_opts:]
    
    version = None
    i = find_in_lines(optlines, 'version:')
    if i != -1:
        version = optlines[i]
        optlines = optlines[:i]        
    
    p = optparse.OptionParser(usage=usage, version=version)    
    try:
        for line in optlines:
            if not line.strip():
                break
            
            default = None
            action = 'store'
            opt, help = line.split(':')[:2]
            if ',' in opt:
                short, long = opt.split(',')[:2]
                short = short.strip()
                type, short = short.split(' ')[:2]
            else:
                short = None
                long = opt.strip()
                type, long = long.split(' ')[:2]
            
            long = long.strip()
            if '=' in long:
                long, default = (s.strip() for s in long.split('='))
                
            if short is None:
                names = (long,)
            else:
                names = (short, long)
            
            type = type.strip()
            help = help.strip()
            if type == 'flag':
                if default is None:
                    default = False
                else:
                    default = bool_build(default)
                
                action = 'store_true' if not default else 'store_false'
                type = None
                
                help = help + (' (boolean switch, %s by default)' % ('on' if default else 'off'))
            
            p.add_option(*names, **{'action':action, 'type':type, 'default':default, 'help':help})
    except (IndexError, ValueError):
        raise ParsingError('Cannot parse the option string correctly, failed on line "%s"' % line)
    
    opts, args = p.parse_args(arglist)
    opts.error = p.error
    opts.args = args
    return opts

